package com.xiezc;

import com.xiezc.model.Device;
import com.xiezc.model.PTypeEnum;
import com.xiezc.model.Packet;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.xiezc.Xutil.packetLen;
import static com.xiezc.Xutil.selfDeviceId;

/**
 * @author wb-xzc291800
 * @date 2018/09/18 17:19
 */
@Slf4j
public class AssembleMsgAndFile {

    @Setter
    private RecvPacketService recvPacketService;

    @Setter
    private SendService sendService;

    @Setter
    private Map<Long, Device> deviceMap;

    @Setter
    private Map<PTypeEnum, MsgHandlerInterface> msgHandlers;

    public static AssembleMsgAndFile single(RecvPacketService recvPacketService,
                                            Map<PTypeEnum, MsgHandlerInterface> msgHandlers) {

        AssembleMsgAndFile assembleMsgAndFile = new AssembleMsgAndFile();
        assembleMsgAndFile.setRecvPacketService(recvPacketService);
        assembleMsgAndFile.setMsgHandlers(msgHandlers);
        return assembleMsgAndFile;
    }

    public void start() {

        dealMsg();
    }

    private void dealMsg() {

        Executors.newScheduledThreadPool(2).scheduleAtFixedRate(() -> {

            ConcurrentHashMap<Long, Set<RecvPacket>> recvPacketBuf =

                recvPacketService.getRecvPacketBuf();

            Iterator<Map.Entry<Long, Set<RecvPacket>>> iterator = recvPacketBuf.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<Long, Set<RecvPacket>> next = iterator.next();
                Set<RecvPacket> value = next.getValue();
                if (value.isEmpty()) {
                    iterator.remove();
                }

                Optional<RecvPacket> any = value.stream().findAny();
                RecvPacket recvPacket = any.get();
                long len = recvPacket.getLen();
                //全部收到包了
                if (len == value.size()) {
                    iterator.remove();
                    Executors.newFixedThreadPool(10).execute(() ->
                        assemAndDeal(value, recvPacket)
                    );
                } else {
                    //没有全部收到包,  看下有没有超时的包, 超时的包丢弃
                    long time = recvPacket.getTime();
                    long allLen = recvPacket.getLen();
                    long l = allLen / 100 + 3;
                    //超时的规则是: 总包数除以100, 再加上3秒 就是超时时间
                    if (time + l * 1000 < System.currentTimeMillis()) {
                        log.error(" id:{} 消息的超时:{}秒,  这个消息丢弃 .... ", next.getKey(), l);
                        iterator.remove();
                    }
                }
            }
        }, 0, 2, TimeUnit.SECONDS);
    }

    /**
     * 组装包
     *
     * @param value
     */
    private ByteBuffer assemMsgPacket(Set<RecvPacket> value) {

        ByteBuffer byteBuffer = null;
        for (RecvPacket packet : value) {
            if (byteBuffer == null) {
                byteBuffer = ByteBuffer.allocate((int)packet.getLen() * packetLen);
            }
            byte[] content = packet.getContent();
            long position = packet.getIndex() * packetLen;
            byteBuffer.position((int)position);
            byteBuffer.put(content);

        }
        byteBuffer.flip();
        return byteBuffer;
    }

    /**
     * 组装包
     *
     * @param value
     */
    private File assemFilePacket(Set<RecvPacket> value, Device device) {

        Long deviceId = device.getId();
        File dir = new File(deviceId + "");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File retFile = null;

        File file = new File(deviceId + File.separator + Xutil.snowflake.generate());
        StringBuilder fileName = new StringBuilder(deviceId + File.separator);
        try (FileChannel channel = new FileOutputStream(file).getChannel()) {
            value.forEach(packet -> {
                byte[] content = packet.getContent();
                long position = packet.getIndex() * packetLen;
                ByteBuffer wrap = ByteBuffer.wrap(content);
                if (packet.getIndex() + 1 == packet.getLen()) {
                    int anInt = wrap.getInt();
                    byte[] bytes = new byte[anInt];
                    wrap.get(bytes);
                    fileName.append(new String(bytes, Xutil.charset));
                    wrap = wrap.slice();
                }
                try {
                    channel.write(wrap, position);
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        retFile = new File(fileName.toString());
        file.renameTo(retFile);
        return retFile;
    }

    private void assemAndDeal(Set<RecvPacket> value, RecvPacket recvPacket) {

        PTypeEnum ptype = recvPacket.getPtype();
        if (ptype == PTypeEnum.File) {
            File file = assemFilePacket(value, recvPacket.getSender());
            msgHandlers.get(ptype).handler(file, recvPacket);
        }
        if (ptype == PTypeEnum.Msg) {
            ByteBuffer byteBuffer = assemMsgPacket(value);
            msgHandlers.get(ptype).handler(byteBuffer, recvPacket);
        }
        if (ptype == PTypeEnum.Login) {

            ByteBuffer byteBuffer = assemMsgPacket(value);
            //处理登录信息
            Object handler = msgHandlers.get(ptype).handler(byteBuffer, recvPacket);

            //回复包的构造
            Packet retPacket = new Packet();
            BeanUtils.copyProperties(recvPacket, retPacket, "content", "ptype", "deviceId");
            retPacket.setPtype(recvPacket.getPtype().getRet());
            retPacket.setDeviceId(selfDeviceId);
            //回复登录失败
            if (!(Boolean)handler) {
                retPacket.setToken(0);
                sendService.addBuf(retPacket, recvPacket.getSender());
                return;
            }
            //回复登录成功
            Device sender = recvPacket.getSender();

            if (sender.getToken() == 0) {
                sender.setConneted(true);
                Device device = deviceMap.get(selfDeviceId);
                sender.setToken(device.getAndRefreshToken());
            }

            //回复令牌
            retPacket.setToken(sender.getToken());
            sendService.addBuf(retPacket, sender);
        }

    }
}
